#pragma once
#ifndef __GEOMETRY_H__
#define __GEOMETRY_H__

#include "Utility.h"

#include "../util/D3DCommon.h"
#include "../util/PreDeclare.h"
#include "Cube.h"

namespace WindGE
{
	/******************************************************************
	* Plane class
	*******************************************************************/
	class WIND_CORE_API Plane
	{
	public:
		Plane() : plane_(0.0f, 0.0f, 0.0f, 0.0f) {}
		Plane(float a, float b, float c, float d) { set(a, b, c, d); }
		Plane(const XMFLOAT3& normal, float d) { set(normal, d); }
		~Plane(void) {}

		inline void set(float a, float b, float c, float d) { plane_.x = a; plane_.y = b; plane_.z = c; plane_.w = d; }
		inline void set(const XMFLOAT3& normal, float d) { plane_.x = normal.x; plane_.y = normal.y; plane_.z = normal.z; plane_.w = d; }

		inline void set_normal(const XMFLOAT3& normal) { plane_.x = normal.x; plane_.y = normal.z; plane_.z = normal.z; }
		inline void set_d(float d) { plane_.w = d; }

		inline float distance(const XMFLOAT3& v) const { return plane_.x*v.x + plane_.y*v.y + plane_.z*v.z + plane_.w; }
		inline void normalize() { XMVECTOR p = XMLoadFloat4(&plane_); XMPlaneNormalize(p); XMStoreFloat4(&plane_, p); }

		inline void project_point_to_plane(XMFLOAT3& projPoint, const XMFLOAT3& targetPoint)
		{
			float fd = distance(targetPoint);
			XMVECTOR tp = XMLoadFloat3(&targetPoint);
			XMVECTOR plane = XMLoadFloat4(&plane_);
			XMVECTOR pp = tp + plane*(-fd);
			XMStoreFloat3(&projPoint, pp);
		}

		inline float normal_dot(const XMFLOAT3& n) const { return plane_.x*n.x + plane_.y*n.y + plane_.z*n.z; }

		operator float*() { return (float*)&plane_; }
		operator const float* () const { return (const float*)&plane_; }

	protected:
		XMFLOAT4 plane_;
	};

	__forceinline bool Plane3DTestCube(const Plane& P, const Cube& R)
	{
		float dCenter = P.distance(R.Center);
		if (dCenter >= 0) return true;

		dCenter = fabs(dCenter);

		if (fabs(P.normal_dot(R.Normal0)) > dCenter
			|| fabs(P.normal_dot(R.Normal1)) > dCenter
			|| fabs(P.normal_dot(R.Normal2)) > dCenter
			|| fabs(P.normal_dot(R.Normal3)) > dCenter)
		{
			return true;
		}
		return false;
	}

	__forceinline bool Plane3DTestCube(const Plane& P, const CubePtr& R)
	{
		float dCenter = P.distance(R->Center);
		if (dCenter >= 0) return true;

		dCenter = fabs(dCenter);

		if (fabs(P.normal_dot(R->Normal0)) > dCenter
			|| fabs(P.normal_dot(R->Normal1)) > dCenter
			|| fabs(P.normal_dot(R->Normal2)) > dCenter
			|| fabs(P.normal_dot(R->Normal3)) > dCenter)
		{
			return true;
		}
		return false;
	}


	/*********************************************************************
	* Frustum class
	**********************************************************************/
	class WIND_CORE_API Frustum
	{
	public:
		Frustum(void)  { }
		~Frustum(void) { }

		inline void extractFromMatrix(const XMMATRIX& clipMatrix);

		inline bool test_cube(const Cube& cube) const;
		inline bool test_cube(CubePtr cube) const;

	public:
		Plane Planes[6];
	};

	inline void Frustum::extractFromMatrix(const XMMATRIX& clipMatrix)
	{
		Planes[0].set(	//left plane
			clipMatrix._14 + clipMatrix._11,
			clipMatrix._24 + clipMatrix._21,
			clipMatrix._34 + clipMatrix._31,
			clipMatrix._44 + clipMatrix._41);

		Planes[1].set(	//right plane
			clipMatrix._14 - clipMatrix._11,
			clipMatrix._24 - clipMatrix._21,
			clipMatrix._34 - clipMatrix._31,
			clipMatrix._44 - clipMatrix._41);

		Planes[2].set(	//top plane
			clipMatrix._14 - clipMatrix._12,
			clipMatrix._24 - clipMatrix._22,
			clipMatrix._34 - clipMatrix._32,
			clipMatrix._44 - clipMatrix._42);

		Planes[3].set(	//bottom plane
			clipMatrix._14 + clipMatrix._12,
			clipMatrix._24 + clipMatrix._22,
			clipMatrix._34 + clipMatrix._32,
			clipMatrix._44 + clipMatrix._42);

		Planes[4].set(	//near plane
			clipMatrix._13,
			clipMatrix._23,
			clipMatrix._33,
			clipMatrix._43);

		Planes[5].set(	//far plane
			clipMatrix._14 - clipMatrix._13,
			clipMatrix._24 - clipMatrix._23,
			clipMatrix._34 - clipMatrix._33,
			clipMatrix._44 - clipMatrix._43);

		for (int i = 0; i < 6; ++i)
		{
			Planes[i].normalize();
		}
	}

	inline bool Frustum::test_cube(const Cube& cube) const
	{
		if (!Plane3DTestCube(Planes[0], cube) ||
			!Plane3DTestCube(Planes[1], cube) ||
			!Plane3DTestCube(Planes[2], cube) ||
			!Plane3DTestCube(Planes[3], cube) ||
			!Plane3DTestCube(Planes[4], cube) ||
			!Plane3DTestCube(Planes[5], cube))
		{
			return false;
		}
		return true;
	}

	inline bool Frustum::test_cube(CubePtr cube) const
	{
		if (!Plane3DTestCube(Planes[0], cube) ||
			!Plane3DTestCube(Planes[1], cube) ||
			!Plane3DTestCube(Planes[2], cube) ||
			!Plane3DTestCube(Planes[3], cube) ||
			!Plane3DTestCube(Planes[4], cube) ||
			!Plane3DTestCube(Planes[5], cube))
		{
			return false;
		}
		return true;
	}
}

#endif // !__GEOMETRY_H__
